﻿using System;
using System.Linq;

namespace OmniKits
{
    using NullGuard;

    public delegate TResult StacklessEventHandler<TArgument, TResult>(object sender, StacklessEventArgs<TArgument, TResult> e);
#pragma warning disable 1591

    public sealed class StacklessEventArgs<TArgument, TResult> : EventArgs
    {
        private object _Sender;
        private StacklessEventHandler<TArgument, TResult> _NextHandler;

        public TArgument Argument { get; set; }

        public StacklessEventArgs(TArgument args = default(TArgument))
        {
            Argument = args;
        }

        private TResult CallNextHandlerInternal(object sender)
        {
            var handler = _NextHandler;
            var last = (StacklessEventHandler<TArgument, TResult>)handler.GetInvocationList().Last();
            _NextHandler = handler - last;

            var result = last(sender, this);

            if (_NextHandler != null)
                return CallNextHandlerInternal(sender);

            return result;
        }

        public TResult InvokeEventHandler(object sender, [AllowNull]StacklessEventHandler<TArgument, TResult> handler)
        {
            if (_NextHandler != null)
                throw new InvalidOperationException();

            if (handler == null)
                throw new NotImplementedException();

            _Sender = sender;
            _NextHandler = handler;

            try
            {
                return CallNextHandlerInternal(sender);
            }
            finally
            {
                _Sender = null;
            }
        }

        public static StacklessEventArgs<TArgument, TResult> Invoke(object sender, StacklessEventHandler<TArgument, TResult> handler, TArgument args = default(TArgument))
        {
            var sea = new StacklessEventArgs<TArgument, TResult>(args);
            sea.InvokeEventHandler(sender, handler);
            return sea;
        }

        public bool Cancel()
        {
            if (_NextHandler == null)
                return true;

            _NextHandler = null;
            return false;
        }

        public TResult Invoke(object sender)
        {
            if (_NextHandler == null)
                throw new InvalidOperationException();

            return CallNextHandlerInternal(sender);
        }

        public TResult Invoke() => this.Invoke(this._Sender);
    }
}
